//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using System.Globalization;

namespace Microsoft.Research.DataLayer
{
    internal class ProviderHelper
    {
        private static string alternateProviderLocation = null;
        private static Dictionary<string, ProviderPkgInfo> providerCache = null;

        internal static string AlternateProviderLocation
        {
            get { return alternateProviderLocation; }
            set { alternateProviderLocation = value; }
        }

        internal static void ReloadProviders()
        {
            providerCache = null;
        }

        internal static ProviderPkgInfo GetProviderByName(string name)
        {
            Dictionary<string, ProviderPkgInfo> provList = GetProviders();

            foreach (ProviderPkgInfo prov in provList.Values)
            {
                if (prov.ProviderKey.Equals(name, StringComparison.InvariantCultureIgnoreCase))
                    return prov;
            }

            return null;
        }

        internal static Dictionary<string, ProviderPkgInfo> GetProviders()
        {
            if (providerCache != null)
                return providerCache;

            ResolveEventHandler resolveHandler = null;
            try
            {
                Dictionary<string, ProviderPkgInfo> ret = new Dictionary<string, ProviderPkgInfo>();
                
                resolveHandler = new ResolveEventHandler(CurrentDomain_AssemblyResolve);
                AppDomain.CurrentDomain.AssemblyResolve += resolveHandler;

                // Determine where we run from
                Assembly me = Assembly.GetExecutingAssembly();
                string myPath = (true == string.IsNullOrEmpty(alternateProviderLocation)) ?
                    Path.GetDirectoryName(me.Location) : alternateProviderLocation;

                // Fetch all assemblies in the folder
                foreach (string asmCandidate in Directory.GetFiles(myPath, "Microsoft.Research.DataLayer.DataProviders.*.dll"))
                {
                    Assembly loadedCandidate = Assembly.LoadFrom(asmCandidate);
                    
                    System.Type provInfo = loadedCandidate.GetType("Microsoft.Research.DataLayer.ProviderInfo");
                    PropertyInfo provInfoInstance = provInfo.GetProperty("Providers", BindingFlags.Public | BindingFlags.Static);
                    ProviderPkgInfo[] provListInCandidate = (ProviderPkgInfo[])provInfoInstance.GetValue(null, null);
                    foreach (ProviderPkgInfo prov in provListInCandidate)
                    {
                        if (ret.ContainsKey(prov.ProviderKey))
                        {
                            // We have a problem reading the provider list. The provider with this key is duplicated in the system
                            throw new ProviderDuplicateException();
                        }
                        ret.Add(prov.ProviderKey, prov);
                    }
                }

                providerCache = ret;

                return ret;
            }
            finally
            {
                if (resolveHandler != null)
                {
                    AppDomain.CurrentDomain.AssemblyResolve -= resolveHandler;
                }
            }
        }

        static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            string asmFilePath = args.Name;
            string asmFile = Path.GetFileName(asmFilePath);

            try
            {
                //In case of assembly info create the dll name and try to load it from the current dir
                //if not try it with the AlternateProviderLocation which will be set from GetProviders()
                //If we receive an actual path split will return only 1 string
                string[] strAssembly = asmFilePath.Split(',');
                if (strAssembly.Length > 1 && !string.IsNullOrEmpty(strAssembly[0]))
                {
                    asmFile = string.Format("{0}.dll", strAssembly[0]);
                }

                Assembly[] asms; 
                asms = AppDomain.CurrentDomain.GetAssemblies(); 
                foreach (Assembly asm in asms) 
                {
                    string loadedAsm = Path.GetFileName(asm.Location);
                    if (string.Equals( loadedAsm, asmFile, StringComparison.InvariantCultureIgnoreCase))
                    {
                        return asm; 
                    }
                }
                Assembly try1 = Assembly.LoadFile(asmFilePath);
                return try1;
            }
            catch
            {
                if (!string.IsNullOrEmpty(AlternateProviderLocation))
                {
                    string asmCandidate = Path.Combine(AlternateProviderLocation, asmFile);

                    if (File.Exists(asmCandidate))
                    {
                        return Assembly.LoadFile(asmCandidate);
                    }
                }
            }
            return null;
        }
    }
}
